package com.cmc6.user.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.IdUtil;
import com.cmc6.common.config.BaseConfig;
import com.cmc6.common.constant.BaseConstant;
import com.cmc6.common.exception.BaseBizCodeEnum;
import com.cmc6.common.model.dto.NotBlankId;
import com.cmc6.common.util.ApiResult;
import com.cmc6.common.util.MyMailUtil;
import com.cmc6.user.exception.BizCodeEnum;
import com.cmc6.user.mapper.UserLoginMapper;
import com.cmc6.user.model.dto.*;
import com.cmc6.user.model.entity.UserLoginDO;
import com.cmc6.user.service.ForgotPasswordService;
import com.cmc6.user.service.UserRegHelperService;
import com.cmc6.user.service.UserService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

@Service
public class ForgotPasswordServiceImpl implements ForgotPasswordService {

    public static final String FORGOT_PASSWORD_PRE_MSG = "<div>请点击下方链接，进行密码重置（十分钟失效）：</div>";

    @Resource
    RedisTemplate<String, Long> redisTemplate;
    @Resource
    RedissonClient redissonClient;
    @Resource
    UserService userService;
    @Resource
    UserLoginMapper userLoginMapper;
    @Resource
    UserRegHelperService userRegHelperService;

    /**
     * 忘记密码-发送链接
     */
    @Override
    public String forgotPassword(EmailNotBlankDTO dto) {

        UserLoginDO userLoginDO = userLoginMapper.loginByEmailOrPhone(dto.getEmail(), null);
        if (userLoginDO == null) {
            ApiResult.error(BizCodeEnum.ACCOUNT_DOES_NOT_EXIST); // 操作失败：账号不存在
        }

        String uuid = IdUtil.simpleUUID();

        ValueOperations<String, Long> ops = redisTemplate.opsForValue();

        // 设置打开页面的 redis key，十分钟过期
        ops.set(BaseConstant.PRE_REDIS_FORGOT_PASSWORD_OPEN + uuid, userLoginDO.getUserId(), 10, TimeUnit.MINUTES);

        String href;
        if (BaseConfig.prodFlag) {
            href = BaseConfig.socketAddress + "/forgotPassword?open=" + uuid;
        } else {
            href = BaseConfig.socketAddress + ":7111/forgotPassword?open=" + uuid;
        }

        // 备注：这里一定要加一个 http:// 不加则 href属性会丢失
        StrBuilder strBuilder = StrBuilder.create("<a href='");
        strBuilder.append(BaseConfig.prodFlag ? "https://" : "http://");
        strBuilder.append(href).append("'>").append(href).append("</a>");

        MyMailUtil.send(dto.getEmail(), "密码重置", FORGOT_PASSWORD_PRE_MSG + strBuilder.toString(), true);

        return BaseBizCodeEnum.API_RESULT_OK.getMsg();
    }

    /**
     * 通过open码获取 code
     */
    @Override
    public String getCodeByOpen(NotBlankId notBlankId) {

        String openRedisKey = BaseConstant.PRE_REDIS_FORGOT_PASSWORD_OPEN + notBlankId.getId();

        ValueOperations<String, Long> ops = redisTemplate.opsForValue();

        // 加锁
        RLock lock = redissonClient.getLock(BaseConstant.PRE_REDISSON + openRedisKey);
        lock.lock();

        try {
            // 通过 open码，获取 userId
            Long userId = Convert.toLong(ops.get(openRedisKey));
            if (userId == null) {
                ApiResult.error(BizCodeEnum.THE_LINK_HAS_EXPIRED_PLEASE_GET_IT_AGAIN); // 链接已过期，请重新获取
            }

            String uuid = IdUtil.simpleUUID();

            // 设置 修改密码时会用到的 code的 redis key，十分钟过期
            ops.set(BaseConstant.PRE_REDIS_FORGOT_PASSWORD_CODE + uuid, userId, 10, TimeUnit.MINUTES);

            redisTemplate.delete(openRedisKey); // 链接只能用一次

            return uuid;
        } finally {
            lock.unlock();
        }

    }

    /**
     * 通过 code修改密码
     */
    @Override
    @Transactional
    public String updatePasswordByCode(UpdatePasswordByCodeDTO dto) {

        String codeRedisKey = BaseConstant.PRE_REDIS_FORGOT_PASSWORD_CODE + dto.getId();

        ValueOperations<String, Long> ops = redisTemplate.opsForValue();

        // 加锁
        RLock lock = redissonClient.getLock(BaseConstant.PRE_REDISSON + codeRedisKey);
        lock.lock();

        try {
            // 通过 code，获取 userId
            Long userId = Convert.toLong(ops.get(codeRedisKey));
            if (userId == null) {
                ApiResult.error(BizCodeEnum.THE_LINK_HAS_EXPIRED_PLEASE_GET_IT_AGAIN); // 链接已过期，请重新获取
            }

            userService.updatePassword(new UserUpdatePasswordDTO(Collections.singleton(userId), dto.getNewPassword(),
                dto.getNewOrigPassword()));

            redisTemplate.delete(codeRedisKey); // code 只能用一次

            // 清除该账号的【登录黑名单】
            redisTemplate.delete(BaseConstant.PRE_REDIS_LOGIN_ACCOUNT_BLACKLIST + userId);

            return BaseBizCodeEnum.API_RESULT_OK.getMsg();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 忘记密码，给手机发送验证码
     */
    @Override
    public String forgotPasswordByPhoneSendCode(PhoneNotBlankDTO dto) {

        return userRegHelperService.phoneSendCode(dto.getPhone(), 3);
    }

    /**
     * 重置密码：通过手机验证码
     */
    @Override
    @Transactional
    public String forgotPasswordByPhone(ForgotPasswordByPhoneDTO dto) {

        String key = BaseConstant.PRE_LOCK_PHONE_REG_CODE + dto.getPhone();

        RLock lock = redissonClient.getLock(BaseConstant.PRE_REDISSON + key);
        lock.lock();

        try {

            // 校验 code
            userRegHelperService.checkCodeByRedis(key, dto.getCode());

            UserLoginDO userLoginDO = userLoginMapper.loginByEmailOrPhone(null, dto.getPhone());
            if (userLoginDO == null) {
                ApiResult.error(BizCodeEnum.ACCOUNT_DOES_NOT_EXIST); // 操作失败：账号不存在
            }

            userService.updatePassword(
                new UserUpdatePasswordDTO(Collections.singleton(userLoginDO.getUserId()), dto.getNewPassword(),
                    dto.getNewOrigPassword()));

            // 清除该账号的【登录黑名单】
            redisTemplate.delete(BaseConstant.PRE_REDIS_LOGIN_ACCOUNT_BLACKLIST + userLoginDO.getUserId());

            return BaseBizCodeEnum.API_RESULT_OK.getMsg();
        } finally {
            lock.unlock();
        }

    }

}
